package com.ruoyi.common.utils.applets;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject;
import com.ruoyi.common.core.domain.dto.AppletsAccessTokenDto;
import com.ruoyi.common.core.domain.dto.Code2SessionDto;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.encryption.AesUtils;
import com.ruoyi.common.utils.http.HttpUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.binary.Base64;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Component;

import java.nio.charset.Charset;
import java.util.Arrays;
import java.util.concurrent.TimeUnit;

/**
 * 小程序工具类
 *
 * @author KevenPotter
 * @date 2024-04-26 15:33:36
 */
@Slf4j
@Component
public class AppletsUtils {

    private static RedisTemplate<String, String> redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate<String, String> redisTemplate) {
        AppletsUtils.redisTemplate = redisTemplate;
    }

    // ----------------------------------------------------------------------
    // |                              一般常量值                              |
    // ----------------------------------------------------------------------
    /*小程序AccessToken键*/
    private static final String APPLETS_ACCESS_TOKEN = "applets_access_token";

    // ----------------------------------------------------------------------
    // |                             小程序请求接口                            |
    // ----------------------------------------------------------------------
    /*code2Session请求地址*/
    private static final String CODE2SESSION_URL = "https://api.weixin.qq.com/sns/jscode2session?appid=APPID&secret=SECRET&js_code=JSCODE&grant_type=authorization_code";
    /*获取小程序AccessToken请求地址*/
    private static final String ACCESS_TOKEN_URL = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid=APPID&secret=SECRET";
    /*订阅消息发送请求地址*/
    public static final String SUBSCRIBE_MESSAGE_SEND_URL = "https://api.weixin.qq.com/cgi-bin/message/subscribe/send?access_token=ACCESS_TOKEN";


    /**
     * 登录凭证校验
     *
     * @param appId  小程序appId
     * @param secret 小程序appSecret
     * @param jsCode 登录时获取的code
     * @return 返回构建好的Session凭证
     * @author KevenPotter
     * @date 2024-04-26 15:33:29
     */
    public static Code2SessionDto code2Session(String appId, String secret, String jsCode) {
        String url = CODE2SESSION_URL.replace("APPID", appId).replace("SECRET", secret).replace("JSCODE", jsCode);
        String result = HttpUtils.doGet(url);
        if (StringUtils.isEmpty(result)) {
            log.error("code2Session request result is null......");
            return null;
        }

        JSONObject jsonMap = JSON.parseObject(result);
        if (MapUtils.isEmpty(jsonMap)) {
            log.error("code2Session request result transfer to JSONObject is null......");
            return null;
        }

        Object openId = jsonMap.get("openid"), sessionKey = jsonMap.get("session_key"), unionid = jsonMap.get("unionid"),
                errcode = jsonMap.get("errcode"), errmsg = jsonMap.get("errmsg");

        Code2SessionDto dto = new Code2SessionDto();
        dto.setOpenId(openId == null ? null : openId.toString());
        dto.setSessionKey(sessionKey == null ? null : sessionKey.toString());
        dto.setUnionId(unionid == null ? null : unionid.toString());
        dto.setErrCode(errcode == null ? null : errcode.toString());
        dto.setErrMsg(errmsg == null ? null : errmsg.toString());
        return dto;
    }

    /**
     * 刷新小程序的AccessToken
     *
     * @param appId  小程序appId
     * @param secret 小程序appSecret
     * @return 返回小程序的AccessToken
     * @author KevenPotter
     * @date 2024-04-26 15:34:08
     */
    public static String refreshAccessToken(String appId, String secret) {
        String url = ACCESS_TOKEN_URL.replace("APPID", appId).replace("SECRET", secret);
        String result = HttpUtils.doGet(url);
        if (StringUtils.isEmpty(result)) {
            throw new RuntimeException("获取小程序AccessToken失败，请求结果为空");
        }
        AppletsAccessTokenDto appletsAccessTokenDto = JSON.parseObject(result, AppletsAccessTokenDto.class);
        redisTemplate.opsForValue().set(APPLETS_ACCESS_TOKEN, appletsAccessTokenDto.getAccessToken(), Integer.parseInt(appletsAccessTokenDto.getExpiresIn()), TimeUnit.SECONDS);
        log.info("applets_access_token: {}", appletsAccessTokenDto.getAccessToken());
        return appletsAccessTokenDto.getAccessToken();
    }

    /**
     * 获取小程序的AccessToken
     *
     * @return 返回小程序的AccessToken
     * @author KevenPotter
     * @date 2024-04-26 15:39:10
     */
    public static String getAccessToken() {
        return redisTemplate.opsForValue().get(APPLETS_ACCESS_TOKEN);
    }

    private static final Charset CHARSET = Charset.forName("utf-8");
    private static final int BLOCK_SIZE = 32;

    /**
     * 获得对明文进行补位填充的字节
     *
     * @param count 填充位数
     * @return 返回补齐用的字节数组
     * @author KevenPotter
     * @date 2024-04-26 15:34:25
     */
    public static byte[] encode(int count) {
        // 计算需要填充的位数
        int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
        // 获得补位所用的字符
        char padChr = chr(amountToPad);
        StringBuilder tmp = new StringBuilder();
        for (int index = 0; index < amountToPad; index++) {
            tmp.append(padChr);
        }
        return tmp.toString().getBytes(CHARSET);
    }

    /**
     * 删除解密后明文的补位字符
     *
     * @param decrypted 解密后的明文
     * @return 返回删除补位字符后的明文
     * @author KevenPotter
     * @date 2024-04-26 15:34:36
     */
    public static byte[] decode(byte[] decrypted) {
        int pad = decrypted[decrypted.length - 1];
        if (pad < 1 || pad > 32) {
            pad = 0;
        }
        return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
    }

    /**
     * 将数字转化成ASCII码对应的字符，用于对明文进行补码
     *
     * @param a 需要转化的数字
     * @return 转化得到的字符
     */
    public static char chr(int a) {
        byte target = (byte) (a & 0xFF);
        return (char) target;
    }

    /**
     * 解密数据
     *
     * @param appId         小程序appid
     * @param encryptedData 加密的数据
     * @param sessionKey    微信端返回的sessionKey
     * @param iv            偏移量
     * @return 返回解密后的数据
     * @author KevenPotter
     * @date 2024-04-26 15:37:46
     */
    public static String decrypt(String appId, String encryptedData, String sessionKey, String iv) {
        String result = "";
        try {
            byte[] resultByte = AesUtils.decrypt(Base64.decodeBase64(encryptedData), Base64.decodeBase64(sessionKey), Base64.decodeBase64(iv));
            if (null != resultByte && resultByte.length > 0) {
                result = new String(decode(resultByte));
                JSONObject jsonObject = JSONObject.parseObject(result);
                String decryptAppid = jsonObject.getJSONObject("watermark").getString("appid");
                if (!appId.equals(decryptAppid)) result = "";
            }
        } catch (Exception e) {
            result = "";
            e.printStackTrace();
        }
        return result;
    }

}
